package dpss.replicamanager;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Vector;

import javax.swing.JOptionPane;

import dpss.commons.CustomLogger;
import dpss.commons.ServerNode;
import dpss.commons.UDPMessage;
import dpss.replicamanager.data.ServerNodes;
import dpss.replicamanager.gui.GUI;

/**
 * Performs actions based on the click from the popup
 * 
 * @author musab.mirza
 * 
 */
public class PopupClickController {

	private static CustomLogger logger = CustomLogger.getInstance();

	// Reference to the application UI and nodes
	private ServerNodes nodes;
	private GUI gui;
	private DatagramSocket socket;

	public PopupClickController(DatagramSocket socket, ServerNodes nodes,
			GUI gui) {
		this.gui = gui;
		this.nodes = nodes;
		this.socket = socket;
		bindPopupActions();
	}

	/**
	 * Binds the popup menu item click actions
	 */
	private void bindPopupActions() {

		gui.getServerNodePopup().getInit_asia()
				.addActionListener(new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent e) {
						actionInitializeServerNode("asia", gui
								.getServerNodePopup().getSelectionKey());
					}

				});

		gui.getServerNodePopup().getInit_eur()
				.addActionListener(new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent e) {
						actionInitializeServerNode("eur", gui
								.getServerNodePopup().getSelectionKey());
					}

				});

		gui.getServerNodePopup().getInit_na()
				.addActionListener(new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent e) {
						actionInitializeServerNode("na", gui
								.getServerNodePopup().getSelectionKey());
					}

				});

		gui.getServerNodePopup().getUninitialize()
				.addActionListener(new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent e) {
						actionUninitializeServerNode(gui.getServerNodePopup()
								.getSelectionKey());
					}

				});

		gui.getServerNodePopup().getDisconnect()
				.addActionListener(new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent e) {
						actionDisconnectServerNode(gui.getServerNodePopup()
								.getSelectionKey());
					}

				});

		gui.getServerNodePopup().getMakeFaulty()
				.addActionListener(new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent e) {
						actionMakeFaultyServerNode(gui.getServerNodePopup()
								.getSelectionKey());
					}

				});

		gui.getServerNodePopup().getMakeUnfaulty()
				.addActionListener(new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent e) {
						actionUnMakeFaultyServerNode(gui.getServerNodePopup()
								.getSelectionKey());
					}


				});
	}

	
	/**
	 * Makes the selected server node unfaulty
	 * @param selectionKey
	 */
	protected void actionUnMakeFaultyServerNode(InetSocketAddress key) {
		ServerNode selectedServerNode = nodes.find(key);
		selectedServerNode.setFaulty(false);
		gui.refresh();
		UDPMessage.sendMessage(socket, new UDPMessage("setFaulty", false, true, ""), selectedServerNode.getSocketAddress());
		logger.log("Setting " + selectedServerNode.getSocketAddress().toString() + " to unfaulty");
	}

	/**
	 * Makes the selected server node faulty
	 * @param selectionKey
	 */
	protected void actionMakeFaultyServerNode(InetSocketAddress key) {
		ServerNode selectedServerNode = nodes.find(key);
		selectedServerNode.setFaulty(true);
		gui.refresh();
		UDPMessage.sendMessage(socket, new UDPMessage("setFaulty", true, true, ""), selectedServerNode.getSocketAddress());
		logger.log("Setting " + selectedServerNode.getSocketAddress().toString() + " to faulty");
	}

	/**
	 * Initializes a server node
	 * 
	 * @param string
	 * @param inetSocketAddress
	 */
	private synchronized void actionInitializeServerNode(String group,
			InetSocketAddress key) {

		ServerNode selectedServerNode = nodes.find(key);

		if (selectedServerNode != null) {

			selectedServerNode.setGroup(group);
			if (!nodes.leaderExists(group))
				selectedServerNode.setLeader(true);

			// Prepare the UDP message to be sent
			HashMap<String, Object> data = new HashMap<String, Object>();
			HashMap<String, InetSocketAddress> otherLeaders = nodes
					.getOtherLeaders(group);
			data.put("otherLeaders", otherLeaders);
			data.put("serverNode", selectedServerNode);
			if (!selectedServerNode.isLeader())
				data.put("myLeader",
						nodes.getLeaderAddress(selectedServerNode.getGroup()));
			else
				data.put("myLeader", null);

			// Send initialization request
			UDPMessage messageToSend = new UDPMessage("initialize", data, true,
					null);
			UDPMessage.sendMessage(socket, messageToSend,
					selectedServerNode.getSocketAddress());

			logger.log("Initialized " + key.toString());
			gui.refresh();

			// If the leader count is 3, then broadast all leader info to all
			// the leaders
			if (nodes.getAllLeaders().size() == 3) {
				broadcastAllLeadersInfoToLeaders();
			}

			// If this was not a leader initialization, then inform the leader
			// of this initiazation
			if (!selectedServerNode.isLeader()) {
				InetSocketAddress leaderAddressForThisReplica = nodes
						.getLeaderAddress(selectedServerNode.getGroup());
				HashSet<InetSocketAddress> allGroupReplicas = nodes
						.getAllGroupReplicas(selectedServerNode.getGroup());
				UDPMessage.sendMessage(socket, new UDPMessage("updateReplicas",
						allGroupReplicas, true, ""),
						leaderAddressForThisReplica);
				logger.log("Sent replicas information to "
						+ selectedServerNode.getGroup() + " leader "
						+ leaderAddressForThisReplica.toString() + ": "
						+ allGroupReplicas.toString());
			}

		}

	}

	/**
	 * Broadcasts all leader info to all leaders so they can update
	 */
	private void broadcastAllLeadersInfoToLeaders() {
		HashSet<ServerNode> allLeaders = nodes.getAllLeaders();
		HashMap<String, InetSocketAddress> leadersInfo = new HashMap<String, InetSocketAddress>();
		for (ServerNode n : allLeaders) {
			leadersInfo.put(n.getGroup(), n.getSocketAddress());
		}
		for (ServerNode n : allLeaders) {
			UDPMessage.sendMessage(socket, new UDPMessage("allLeadersInfo",
					leadersInfo, true, ""), n.getSocketAddress());
		}
		logger.log("Broadcasted all leaders info to all the leaders: "
				+ allLeaders.toString());
	}

	/**
	 * Initializes a server node
	 * 
	 * @param string
	 * @param inetSocketAddress
	 */
	private synchronized void actionUninitializeServerNode(InetSocketAddress key) {

		ServerNode selectedServerNode = nodes.find(key);
		String originalGroup = selectedServerNode.getGroup();

		if (selectedServerNode != null && !selectedServerNode.isLeader()) {

			selectedServerNode.setGroup("uninitialized");
			selectedServerNode.setFailCount(0);

			// Send uninitialize message
			UDPMessage messageToSend = new UDPMessage("uninitialize", null,
					true, null);
			UDPMessage.sendMessage(socket, messageToSend,
					selectedServerNode.getSocketAddress());

			logger.log("Uninitialized " + key.toString());
			gui.refresh();

			// If this was not a leader initialization, then inform the leader
			// of this initiazation
			if (!selectedServerNode.isLeader()) {
				InetSocketAddress leaderAddressForThisReplica = nodes
						.getLeaderAddress(originalGroup);
				HashSet<InetSocketAddress> allGroupReplicas = nodes
						.getAllGroupReplicas(originalGroup);
				UDPMessage.sendMessage(socket, new UDPMessage("updateReplicas",
						allGroupReplicas, true, ""),
						leaderAddressForThisReplica);
				logger.log("Sent replicas information to "
						+ selectedServerNode.getGroup() + " leader "
						+ leaderAddressForThisReplica.toString() + ": "
						+ allGroupReplicas.toString());
			}

		}
	}

	/**
	 * Disconnects a server node
	 * 
	 * @param string
	 * @param key
	 */
	private synchronized void actionDisconnectServerNode(InetSocketAddress key) {

		ServerNode selectedServerNode = nodes.find(key);
		String originalGroup = selectedServerNode.getGroup();
		InetSocketAddress leaderAddressForThisReplica = nodes
				.getLeaderAddress(originalGroup);

		nodes.removeByKey(key);
		UDPMessage messageToSend = new UDPMessage("disconnect", null, true, "");
		UDPMessage.sendMessage(socket, messageToSend, key);
		logger.log("Disconnected " + key.toString());
		gui.refresh();

		HashSet<InetSocketAddress> allGroupReplicas = nodes
				.getAllGroupReplicas(selectedServerNode.getGroup());
		UDPMessage.sendMessage(socket, new UDPMessage("updateReplicas",
				allGroupReplicas, true, ""), leaderAddressForThisReplica);

		logger.log("Sent replicas information to "
				+ selectedServerNode.getGroup() + " leader "
				+ leaderAddressForThisReplica.toString() + ": "
				+ allGroupReplicas.toString());

	}

}
